#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
// #include "ThreadPool-Singleton.hpp"

// using task_t = std::function<void()>;

class TcpServer;
struct ThreadData
{
    int _fd;
    InetAddr _addr;
    TcpServer* _tsvr;
    ThreadData(int fd, InetAddr addr, TcpServer* tvr)
        :_fd(fd), _addr(addr), _tsvr(tvr)
    {}
};

enum
{
    USE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
};

static const int defaultFd = -1;
static const int gBackLog = 6;

class TcpServer
{
public:
    TcpServer(uint16_t port)
        : _port(port), _sockfd(defaultFd), _isrunning(false)
    {
    }
    void InitServer()
    {
        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket create error\n");
            exit(SOCKET_ERR);
        }
        LOG(INFO, "socket create success!\n");

        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        int n = ::bind(_sockfd, reinterpret_cast<struct sockaddr *>(&local), sizeof local);
        if (n < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERR);
        }
        LOG(INFO, "bind sueecess!\n");

        n = ::listen(_sockfd, gBackLog);
        if (n < 0)
        {
            LOG(FATAL, "listen error\n");
            exit(LISTEN_ERR);
        }
        LOG(INFO, "listen success!\n");
    }
    void Service(int fd, InetAddr addr)
    {
        _isrunning = true;
        char buff[1024];
        while(true)
        {
            //1. 获取数据
            ssize_t n = recv(fd, buff, sizeof(buff) - 1, 0);
            if(n > 0)
            {
                //2. 处理数据
                buff[n] = 0;
                LOG(INFO, "client[%s:%d] say# %s\n", addr.IP().c_str(), addr.Port(), buff);

                //3. 发送数据
                ::send(fd, buff, strlen(buff), 0);
            }
            else 
            {
                // =0 quit
                // <0 error
                LOG(INFO, "client[%s:%d] quit....done\n", addr.IP().c_str(), addr.Port());
                break;
            }
        }
        ::close(fd);
    }
    static void* HandlerSock(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = reinterpret_cast<ThreadData*>(args);
        td->_tsvr->Service(td->_fd, td->_addr);
        delete td;
        return nullptr;
    }
    void Loop()
    {
        _isrunning = true;
        while(_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            int fd = ::accept(_sockfd, reinterpret_cast<struct sockaddr*>(&peer), &len);
            if(fd < 0)
            {
                LOG(WARNING, "accept error\n");
                continue;
            }
            InetAddr addr(peer);
            LOG(INFO, "get a link [%s:%d]\n", addr.IP().c_str(), addr.Port());
            //version 1  每次只有一个客户端可以连接上服务器
            // Service(fd, peer);


            //version 2 多进程
            //设置对SIGCHLD的忽略
            // signal(SIGCHLD, SIG_IGN);
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     ::close(_sockfd); //只关心fd，不关心listensock
            //     if(fork() > 0) exit(0);
            //     Service(fd, peer);
            //     exit(0);
            // }
            // ::close(fd);

            //version 3 线程池
            // task_t t = std::bind(&TcpServer::Service, this, fd, addr);
            // ThreadPool<task_t>::GetInstance()->Enqueue(t);

            //version 4 多线程
            pthread_t tid;
            ThreadData* td = new ThreadData(fd, addr, this);
            pthread_create(&tid, nullptr, HandlerSock, td);
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;
};